#!/usr/bin/env python
# Photo Grid Print utility
# Copyright 2007 (C) Ralph Thomas <ralpht@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import gtk
import gtk.glade
import cairo
import sys
import urllib
from math import sqrt, pi

PORTRAIT = 0
LANDSCAPE = 1
SQRT2 = sqrt(2)
#
# The spacing between photos as a percentage of the
# longest dimension of the page.
#
SPACING = 1
#
# Return a filename from a filename or file:// URI.
#
def get_path(uri):
	path = urllib.url2pathname(uri) # escape special chars
	path = path.strip('\r\n\x00') # remove \r\n and NULL

	# get the path to file
	if path.startswith('file:\\\\\\'): # windows
		path = path[8:] # 8 is len('file:///')
	elif path.startswith('file://'): # nautilus, rox
		path = path[7:] # 7 is len('file://')
	elif path.startswith('file:'): # xffm
		path = path[5:] # 5 is len('file:')
	return path

class main_window:
	def set_rows(self, w):
		self.rows = w.get_value_as_int()
		self.preview.queue_draw()

	def set_columns(self, w):
		self.columns = w.get_value_as_int()
		self.preview.queue_draw()

	def set_tile(self, w):
		self.tile = w.get_active()
		self.preview.queue_draw()

	def draw_page(self, operation, context, page_nr):
		ctx = context.get_cairo_context()
		self.draw_images(ctx, 0, 0, context.get_width(), context.get_height(), False)

	def action_print(self, w):
		print_op = gtk.PrintOperation()
		print_op.set_n_pages(1)
		if self.settings == None:
			self.settings = gtk.PrintSettings()
		print_op.set_print_settings(self.settings)
		print_op.connect('draw_page', self.draw_page)
		res = print_op.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG, self.main_window)
		if res == gtk.PRINT_OPERATION_RESULT_APPLY:
			self.settings = print_op.get_print_settings()

	def action_close(self, w):
		gtk.main_quit()

	def get_spacing(self, width, height):
		if height > width:
			return float(SPACING * height / 100.0)
		return float(SPACING * width / 100.0)

	def draw_images(self, context, x, y, width, height, use_thumbnails):
		spacing = self.get_spacing(width, height)
		item_width = float(width - spacing * (self.columns - 1)) / float(self.columns)
		item_height = float(height - spacing * (self.rows - 1)) / float(self.rows)
		item_xoff = item_width + spacing
		item_yoff = item_height + spacing
		item_aspect = item_width / item_height
		#
		# draw the images!
		#
		context.set_source_rgb(0, 0, 0)
		image = 0
		for row in range(self.rows):
			for column in range(self.columns):
				xoff = x + column * item_xoff
				yoff = y + row * item_yoff
				pixbuf = self.thumbnails[image]
				if use_thumbnails == False:
					#
					# Load the real image at full resolution.
					#
					pixbuf = gtk.gdk.pixbuf_new_from_file(get_path(self.filenames[image]))
				#
				# Use this helper in the GTK cairo context to set the source
				# from a pixbuf.
				#
				context.set_source_pixbuf(pixbuf, 0, 0)
				#
				# Now we need to change the matrix of the source pattern so
				# that the image is scaled and translated to be drawn at
				# the correct location.
				#
				pixbuf_pattern = context.get_source()
				transform = pixbuf_pattern.get_matrix()
				#
				# If the image aspect ratio and the space's aspect ratio are
				# different sides of 1.0 then we rotate the image.
				#
				image_aspect = float(pixbuf.get_width()) / float(pixbuf.get_height())
				horizontal_aspect = float(pixbuf.get_width()) / float(item_width)
				vertical_aspect = float(pixbuf.get_height()) / float(item_height)
				out_width = item_width
				out_height = item_height
				image_width = float(pixbuf.get_width())
				image_height = float(pixbuf.get_height())
				if ((image_aspect < 1.0 and item_aspect > 1.0) or (image_aspect > 1.0 and item_aspect < 1.0)):# and False:
					transform.rotate(pi / 2)
					transform.translate(0, -pixbuf.get_width())
					#
					# We rotates the image, so the aspect ratios must be
					# recalculated.
					#
					image_aspect = 1.0 / image_aspect
					horizontal_aspect = float(pixbuf.get_height()) / float(item_width)
					vertical_aspect = float(pixbuf.get_width()) / float(item_height)
					image_width = image_height
					image_height = float(pixbuf.get_width())
				#
				# Set the scale so that the image fits in the box, using
				# maximum area without overflowing.
				#
				if image_aspect > item_aspect:
					transform.scale(horizontal_aspect, horizontal_aspect)
					out_height = image_height / horizontal_aspect
				else:
					transform.scale(vertical_aspect, vertical_aspect)
					out_width = image_width / vertical_aspect
				#
				# Translate the pattern to the start of this box and set
				# the transformation matrix into the pattern.
				#
				transform.translate(-xoff, -yoff)
				pixbuf_pattern.set_matrix(transform)
				#
				# Work around Cairo bug #9983 by repeating the pattern
				# but only drawing it once.
				#
				pixbuf_pattern.set_extend(cairo.EXTEND_REPEAT)
				#
				# Draw the rectangle that will be filled with the
				# image.
				#
				context.rectangle(xoff, yoff, out_width, out_height)
				context.fill()
				image += 1
				if image >= len(self.thumbnails):
					if self.tile:
						image = 0
					else:
						return

	def draw_preview(self, widget, event):
		context = widget.window.cairo_create()
		context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
		context.clip()

		rect = widget.get_allocation()

		widget_ratio = rect.width / (rect.height + 0.00)
		paper_ratio = 1 / SQRT2

		w = rect.width * 5 / 6
		h = rect.height * 5 / 6

		if widget_ratio < paper_ratio:
			h = w * SQRT2
		else:
			w = h / SQRT2

		x = rect.x + (rect.width - w) / 2
		y = rect.y + (rect.height - h) / 2
		context.rectangle(x, y, w, h)
		context.set_source_rgb(1.0, 1.0, 1.0)
		context.fill()

		spacing = self.get_spacing(w, h)
		self.draw_images(context, x + spacing, y + spacing, w - 2 * spacing, h - 2 * spacing, True)

		return False

	def __init__(self):
		self.rows = 3
		self.columns = 5
		self.settings = None
		self.tile = False
		#
		# What we do:
		#
		#  0. (Load up please wait dialog) -- not implemented yet!
		#  1. Load up small versions (max. 512x512) of images
		#  2. Load up and connect UI
		#
		self.filenames = sys.argv[1:] #SOURCE_FILES
		self.thumbnails = []
		for uri in self.filenames:
			#
			# Load thumbnails.
			#
			thumb = gtk.gdk.pixbuf_new_from_file_at_scale(get_path(uri), 512, 512, True)
			self.thumbnails.append(thumb)
		#
		# Load up the main UI
		#
		ui = gtk.glade.XML('/usr/share/photo-grid-print/photo-grid-print.glade')
		#
		# Setup the preview pane. We want it to have a reasonable minimum size
		# and a grey background. We also hookup the expose method here, although
		# it would probably be better done in the glade file.
		#
		self.preview = ui.get_widget('preview')
		self.preview.set_size_request(370, 422)
		self.preview.connect('expose_event', self.draw_preview)
		grey = gtk.gdk.color_parse('#808080')
		grey = self.preview.get_colormap().alloc_color(grey)
		self.preview.modify_bg(gtk.STATE_NORMAL, grey)
		#
		# Quit when the window is closed.
		#
		self.main_window = ui.get_widget('main_window')
		self.main_window.connect('destroy', gtk.main_quit);
		self.main_window.show_all()
		signals = {
			'set_rows' : self.set_rows,
			'set_columns' : self.set_columns,
			'set_tile' : self.set_tile,
			'action_print' : self.action_print,
			'action_close' : (gtk.main_quit)
			}
		ui.signal_autoconnect(signals)

if __name__ == '__main__':
	app = main_window()
	gtk.main()

